﻿/*

 * Copyright (c) 2013 Diorge Brognara, Renan Peixoto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;
using System.Collections.Generic;
using System.IO;

namespace Grafos {
    /// <summary>
    /// Representa uma matriz de adjacência de um grafo
    /// </summary>
    /// <remarks>
    /// Salva e carrega matrizes de adjacência do disco para a memória
    /// Estas matrizes podem ser convertidas em grafos, ou geradas de grafos em memória
    /// </remarks>
    public class MatrizDeAdjacencia {

        /// <summary>
        /// Indica se deve ser lida uma matriz ponderada ou não
        /// </summary>
        /// <remarks>
        /// Padrão: false
        /// </remarks>
        public bool Ponderada { get; set; }

        private bool[,] matriz;
        private float[,] matrizPonderada;
        private string[] nomes;
        private bool carregouNormal;
        private bool carregouPonderada;

        /// <summary>
        /// Instancia uma nova matriz de adjacência, pronta para ler uma matriz não ponderada
        /// </summary>
        public MatrizDeAdjacencia() {
            Ponderada = false;
            matriz = null;
            matrizPonderada = null;
            nomes = null;
            carregouNormal = false;
            carregouPonderada = false;
        }

        /// <summary>
        /// Retorna a lista de nomes lidos
        /// </summary>
        /// <returns>Conjunto de todos os nomes que foram lidos do arquivo</returns>
        /// <exception cref="InvalidOperationException">Nenhuma matriz foi carregada com sucesso para a memória</exception>
        public string[] GetNomes() {
            if (carregouNormal || carregouPonderada)
                return nomes;
            else
                throw new InvalidOperationException("Não há matriz carregada corretamente na memória");
        }

        /// <summary>
        /// Retorna a matriz não-ponderada lida
        /// </summary>
        /// <returns>Matriz de adjacência presente no arquivo lido</returns>
        /// <exception cref="InvalidOperationException">A matriz carregada é ponderada, ou não foi carregada nenhuma matriz</exception>
        public bool[,] GetMatriz() {
            if (!carregouNormal)
                throw new InvalidOperationException("Não há matriz não-ponderada carregada corretamente na memória");
            return matriz;
        }

        /// <summary>
        /// Retorna a matriz ponderada lida
        /// </summary>
        /// <returns>Matriz de adjacência presente no arquivo lido</returns>
        /// <exception cref="InvalidOperationException">A matriz carregada não é ponderada, ou não foi carregada nenhuma matriz</exception>
        public float[,] GetMatrizPonderada() {
            if (!carregouPonderada)
                throw new InvalidOperationException("Não há matriz ponderada carregada corretamente na memória");
            return matrizPonderada;
        }

        /// <summary>
        /// Carrega uma matriz do disco
        /// </summary>
        /// <param name="arquivoNomes">Arquivo contendo os nomes dos vértices</param>
        /// <param name="arquivoMatriz">Arquivo contendo a matriz de adjacência</param>
        /// <remarks>
        /// Considerando N o número de vértices
        /// Formato do arquivo de nomes:
        /// Um nome de vértice por linha, N linhas no arquivo
        /// Formato do arquivo de matriz:
        /// Uma linha da matriz por linha do arquivo, N linhas no arquivo
        /// Cada linha deve conter N valores numéricos separados por ' ' (espaço em branco)
        /// Ver exemplo para maiores detalhes
        /// Na matriz não-ponderada, todo valor maior que zero é considerado como existente, igual ou menor a zero é inexistente
        /// </remarks>
        /// <exception cref="FileNotFoundException">Um dos arquivos não pôde ser lido</exception>
        /// <exception cref="FormatException">Formato da matriz de adjacência incorreto</exception>
        /// <example>
        /// Não-ponderado:
        /// <code>
        /// string[] nomes;
        /// bool[,] matriz;
        /// MatrizDeAdjacencia mda;
        /// mda = new MatrizDeAdjacencia();
        /// mda.Carregar("cidades_nomes.txt", "cidades.txt");
        /// nomes = mda.GetNomes(); // { Araraquara, Ibaté, São Carlos }
        /// matriz = mda.GetMatriz(); //  { false, true, true,
        ///                           //    true, false, true,
        ///                           //    true, true, false }
        /// </code>
        /// Ponderado:
        /// <code>
        /// string[] nomes;
        /// float[,] matriz;
        /// MatrizDeAdjacencia mda;
        /// mda.Ponderada = true;
        /// mda = new MatrizDeAdjacencia();
        /// mda.Carregar("cidades_nomes.txt", "cidades.txt");
        /// nomes = mda.GetNomes(); // { Araraquara, Ibaté, São Carlos }
        /// matriz = mda.GetMatrizPonderada();  // { 0, 1, 2,
        ///                                     //   1, 0, 1,
        ///                                     //   2, 1, 0 }
        /// </code>
        /// Arquivos:
        /// "cidades_nomes.txt":
        /// Araraquara
        /// Ibaté
        /// São Carlos
        /// "cidades.txt":
        /// 0 1 2
        /// 1 0 1
        /// 2 1 0
        /// </example>
        public void Carregar(string arquivoNomes, string arquivoMatriz) {
            string linha;
            List<string> listaNomes;
            string[] valores;
            int i, j;
            float val;

            if (!File.Exists(arquivoNomes))
                throw new FileNotFoundException("Arquivo de nomes não encontrado");
            if (!File.Exists(arquivoMatriz))
                throw new FileNotFoundException("Arquivo de matriz não encontrado");

            carregouNormal = false;
            carregouPonderada = false;
            listaNomes = new List<string>();

            using (StreamReader reader = new StreamReader(arquivoNomes)) {

                while ((linha = reader.ReadLine()) != null) {
                    listaNomes.Add(linha);
                }

                reader.Close();
            }

            matriz = new bool[listaNomes.Count, listaNomes.Count];
            matrizPonderada = new float[listaNomes.Count, listaNomes.Count];
            j = 0;

            using (StreamReader reader = new StreamReader(arquivoMatriz)) {

                while ((linha = reader.ReadLine()) != null) {
                    valores = linha.Split(' ');
                    if (valores.Length != listaNomes.Count)
                        throw new FormatException("Formato da matriz incorreto");
                    for (i = 0; i < valores.Length; i++) {
                        if (!float.TryParse(valores[i], out val))
                            throw new FormatException("Valor não numérico na matriz");
                        if (Ponderada)
                            matrizPonderada[i, j] = val;
                        else
                            matriz[i, j] = val > 0;
                    }
                    j++;
                }

            }

            carregouNormal = !Ponderada;
            carregouPonderada = Ponderada;
            nomes = listaNomes.ToArray();
        }

        /// <summary>
        /// Salva uma matriz não-ponderada no disco
        /// </summary>
        /// <param name="arquivoNomes">Arquivo onde gravar os nomes dos vértices</param>
        /// <param name="arquivoMatriz">Arquivo onde gravar a matriz de adjacência</param>
        /// <param name="nomes">Lista de nomes dos vértices</param>
        /// <param name="matriz">Valor da matriz de adjacência</param>
        /// <exception cref="ArgumentException">Os conjuntos não formam um grafo</exception>
        public void Salvar(string arquivoNomes, string arquivoMatriz, string[] nomes, bool[,] matriz) {
            int i, j;
            int width, height;

            width = matriz.GetLength(0);
            height = matriz.GetLength(1);

            if (nomes.Length != width || nomes.Length != height)
                // transitividade: || width != height
                throw new ArgumentException("Os conjuntos de nomes e a matriz não tem tamanhos compatíveis com uma matriz de adjacência");

            using (StreamWriter writer = new StreamWriter(arquivoNomes)) {
                foreach (string s in nomes) {
                    writer.WriteLine(s);
                }
                writer.Flush();
                writer.Close();
            }

            using (StreamWriter writer = new StreamWriter(arquivoMatriz)) {

                for (j = 0; j < height; j++) {
                    for (i = 0; i < width; i++) {
                        writer.Write(matriz[i, j] ? 1f : 0f);
                        writer.Write(' ');
                    }
                    writer.WriteLine();
                }

                writer.Flush();
                writer.Close();
            }

        }


        /// <summary>
        /// Salva uma matriz ponderada no disco
        /// </summary>
        /// <param name="arquivoNomes">Arquivo onde gravar os nomes dos vértices</param>
        /// <param name="arquivoMatriz">Arquivo onde gravar a matriz de adjacência</param>
        /// <param name="nomes">Lista de nomes dos vértices</param>
        /// <param name="matriz">Valor da matriz de adjacência</param>
        /// <exception cref="ArgumentException">Os conjuntos não formam um grafo</exception>
        public void Salvar(string arquivoNomes, string arquivoMatriz, string[] nomes, float[,] matriz) {
            int i, j;
            int width, height;

            width = matriz.GetLength(0);
            height = matriz.GetLength(1);

            if (nomes.Length != width || nomes.Length != height)
                // transitividade: || width != height
                throw new ArgumentException("Os conjuntos de nomes e a matriz não tem tamanhos compatíveis com uma matriz de adjacência");

            using (StreamWriter writer = new StreamWriter(arquivoNomes)) {
                foreach (string s in nomes) {
                    writer.WriteLine(s);
                }
                writer.Flush();
                writer.Close();
            }

            using (StreamWriter writer = new StreamWriter(arquivoMatriz)) {

                for (j = 0; j < height; j++) {
                    for (i = 0; i < width; i++) {
                        writer.Write(matriz[i, j]);
                        writer.Write(' ');
                    }
                    writer.WriteLine();
                }

                writer.Flush();
                writer.Close();
            }
        }

    }
}
